/*
 * Copyright (C) 2005, Velocityme Partnership
 * TaskTreeGraph.java
 *
 * Created on 31 October 2005, 08:56
 */

package com.velocityme.utility;

import com.velocityme.entity.Anchor;
import com.velocityme.entity.Task;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;

/**
 *
 * @author  Robert
 */
public class TaskTreeGraph {
    
    private static final org.apache.commons.logging.Log m_log = org.apache.commons.logging.LogFactory.getLog(TaskTreeGraph.class);

    public abstract class Node {
        private Task m_task;
        private long m_time;
        private boolean m_isFixed;
        public Node(Task task, Date p_date, boolean p_isFixed) {
            m_task = task;
            m_time = p_date != null? p_date.getTime(): Long.MIN_VALUE;
            m_isFixed = p_isFixed;
            if (task instanceof Anchor) {
                // time for anchors is always fixed at the user specified time
                m_time = task.getStartDate().getTime();
                m_isFixed = true;
            }
        }
        public long getTime() { return m_time; }
        public void setTime(long p_time) { m_time = p_time; }
        public boolean isFixed() { return m_isFixed; }
        public Task getTask() { return m_task; }
        public void assignTime() {}
        public void mergeTime(long p_time) {
//            m_log.info("merge time: " + getTaskLocal().getNodeLocal().getName() + Boolean.toString(isFixed()));
//            m_log.info("p_time: " + Long.toString(p_time));
//            m_log.info("getTime: " + Long.toString(getTime()));
            if (!isFixed())
                setTime(Math.max(getTime(), p_time));
        }
    }
    
    public class StartNode extends Node {
        public StartNode(Task task) {
            super(task, task.getCachedStartDate(), task.getCachedStartDate() != null);
        }
        @Override
        public void assignTime() {
            getTask().setStartDate(new Date(getTime()));
        }
    }
    
    public class StopNode extends Node {
        public StopNode(Task task) {
	    super(task, task.getCachedStopDate(), false);//(int)p_taskLocal.getPercentageComplete().byteValue() == 100);
        }
        @Override
        public void assignTime() {
            getTask().setStopDate(new Date(getTime()));
        }
    }
    
    public class Vertex {
        private Node m_source;
        private Node m_dest;
        private long m_offset;
        public Vertex(Node p_source, Node p_dest, long p_offset) {
            m_source = p_source;
            m_dest = p_dest;
            m_offset = p_offset;
        }
        public long getOffset() { return m_offset; }
        public void setOffset(long p_offset) { m_offset = p_offset; }
        public void apply() { m_dest.mergeTime(m_source.getTime() + m_offset); }
        public Node getDestNode() { return m_dest; }
    }
    
    private Map<Node, Collection<Vertex>> m_sourceVertices = new HashMap<Node, Collection<Vertex>>(); //[sourceNode]
    private Map<Node, Collection<Vertex>> m_destVertices = new HashMap<Node, Collection<Vertex>>(); //[destNode]
    private Map<Task, Node> m_startNodes = new HashMap<Task, Node>(); //[taskLocal]
    private Map<Task, Node> m_stopNodes = new HashMap<Task, Node>(); //[taskLocal]
    
    /** Creates a new instance of TaskTreeGraph */
    public TaskTreeGraph() {
    }
    
    public void addTask(Task task) {
//        m_log.info("Adding task: " + p_taskLocal.getNodeLocal().getName());
        StartNode startNode = new StartNode(task);
        StopNode stopNode = new StopNode(task);
        m_startNodes.put(task, startNode);
        m_stopNodes.put(task, stopNode);
        m_sourceVertices.put(startNode, new ArrayList<Vertex>());
        m_sourceVertices.put(stopNode, new ArrayList<Vertex>());
        m_destVertices.put(startNode, new ArrayList<Vertex>());
        m_destVertices.put(stopNode, new ArrayList<Vertex>());
    }
    
    private void addVertex(Node p_sourceNode, Node p_destNode, long offset) {
//        m_log.info("Adding vertex: " + p_sourceNode.getTaskLocal().getNodeLocal().getName() + " " + p_destNode.getTaskLocal().getNodeLocal().getName() + " " + Long.toString(offset));
        Vertex vertex = new Vertex(p_sourceNode, p_destNode, offset);
        m_sourceVertices.get(p_sourceNode).add(vertex);
        m_destVertices.get(p_destNode).add(vertex);
    }
    
    public void addStartStartVertex(Task sourceTask, Task destTask, long offset) {
        Node sourceNode = m_startNodes.get(sourceTask);
        Node destNode = m_startNodes.get(destTask);
        addVertex(sourceNode, destNode, offset);
    }
    
    public void addStartStopVertex(Task sourceTask, Task destTask, long offset) {
        Node sourceNode = m_startNodes.get(sourceTask);
        Node destNode = m_stopNodes.get(destTask);
        addVertex(sourceNode, destNode, offset);
    }
    
    public void addStopStartVertex(Task sourceTask, Task destTask, long offset) {
        Node sourceNode = m_stopNodes.get(sourceTask);
        Node destNode = m_startNodes.get(destTask);
        addVertex(sourceNode, destNode, offset);
    }
    
    public void addStopStopVertex(Task sourceTask, Task destTask, long offset) {
        Node sourceNode = m_stopNodes.get(sourceTask);
        Node destNode = m_stopNodes.get(destTask);
        addVertex(sourceNode, destNode, offset);
    }
    
    public void mergeCurrentTime(Task rootTask, long now) {
        Node rootNode = m_startNodes.get(rootTask);
//        m_log.info("Start isFixed: " + Boolean.toString(rootNode.isFixed()));
        rootNode.mergeTime(now);
    }
    
    public boolean isFinished() {
//        m_log.info("Num Sources " + m_sourceVertices.size());
        return m_sourceVertices.isEmpty();
    }
    
    public void setDuration(Node node, long duration) {
//        m_log.info("Set duration: " + p_node.getTaskLocal().getNodeLocal().getName() + " " + Long.toString(p_duration));
        for (Vertex vertex: m_sourceVertices.get(node)) {
            if (vertex.getOffset() == -1L) {
                vertex.setOffset(duration);
                break;
            }
        }
    }
    
    public Node getNextNode() {
        for (Node node: m_destVertices.keySet()) {
            if (m_destVertices.get(node).isEmpty()) {
                // no more dependencies so can assign time now!
                node.assignTime();
//                m_log.info("Next Node: " + node.getClass().getName() + " " + node.getTaskLocal().getNodeLocal().getName());
                return node;
            }
        }
        return null;
    }
    
    public void completeNode(Node node) {
//        m_log.info("Complete Node: " + p_node.getTaskLocal().getNodeLocal().getName() + " " + Long.toString(p_node.getTime()));
        // apply all vertices from this node and clean up
        Collection<Vertex> sourceVerticesForNode = m_sourceVertices.get(node);
        for (Vertex vertex: sourceVerticesForNode) {
            // apply the vertex
//            m_log.info("Apply vertex: " + vertex.getDestNode().getTaskLocal().getNodeLocal());
//            m_log.info("Before: " + vertex.getDestNode().getTime());
            vertex.apply();
//            m_log.info("After: " + vertex.getDestNode().getTime());
            // then remove from dest collection
            Collection<Vertex> destVertices = m_destVertices.get(vertex.getDestNode());
            destVertices.remove(vertex);
        }
        // now remove all from source collection
        sourceVerticesForNode.clear();
        // then we can remove collection
        m_sourceVertices.remove(node);
        m_destVertices.remove(node);
        // and finally remove the node
        if (node instanceof StartNode)
            m_startNodes.remove(node.getTask());
        else
            m_stopNodes.remove(node.getTask());
    }
    
}
